package utils

import (
	"context"
	"fmt"
	"nexus/internal/config"
	"time"

	"nexus/utils/errorx"

	"github.com/golang-jwt/jwt/v4"
	"github.com/zeromicro/go-zero/core/logc"
)

type JWT struct {
	Secret []byte
	Issuer string
	Expire time.Duration
}

func NewJWT(config config.Config) *JWT {
	return &JWT{
		Secret: []byte(config.JWT.Secret),
		Issuer: config.JWT.Issuer,
		Expire: time.Duration(config.JWT.Expire) * time.Second,
	}
}

type JWTClaim struct {
	jwt.RegisteredClaims
	UserID   string `json:"user_id"`
	Nickname string `json:"nickname"`
	TenantID string `json:"tenant_id"`
}

// GenerateToken 根据用户信息生成JWT令牌
func (j *JWT) GenerateToken(userID, nickname, tenantID string) (string, error) {
	claims := JWTClaim{
		UserID:   userID,
		Nickname: nickname,
		TenantID: tenantID,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(j.Expire)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    j.Issuer,
			Subject:   userID,
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.Secret)
}

// ParseToken 解析 Token
func (j *JWT) ParseToken(tokenString string) (*JWTClaim, error) {
	token, err := jwt.ParseWithClaims(tokenString, &JWTClaim{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return j.Secret, nil
	})

	if err != nil {
		logc.Errorf(context.Background(), "Token解析错误: %v", err)
		return nil, errorx.TokenInvalid
	}

	if claims, ok := token.Claims.(*JWTClaim); ok && token.Valid {
		return claims, nil
	}

	return nil, errorx.TokenInvalid
}

// RefreshToken 刷新 Token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
	claims, err := j.ParseToken(tokenString)
	if err != nil {
		logc.Errorf(context.Background(), "Token刷新错误: %v", err)
		return "", errorx.TokenInvalid
	}

	// 如果 Token 即将过期（剩余时间小于一半），允许刷新
	if time.Until(claims.ExpiresAt.Time) < j.Expire/2 {
		return j.GenerateToken(claims.UserID, claims.Nickname, claims.TenantID)
	}

	return tokenString, nil
}
